Java JavaScript Python C# C C++ Go Kotlin PHP Swift R Ruby TypeScript Scala SQL Perl rust VisualBasic Matlab Julia

Generics → Introduction to Generics

Generics

Introduction to Generics

Introduction to Generics in Java

Generics, introduced in Java 5, are a powerful feature that enhances type safety and code reusability. Before generics, collections like `ArrayList` could hold objects of any type. This led to potential runtime errors because you had to manually cast objects retrieved from the collection, and the compiler couldn't guarantee type correctness. Generics solve this by allowing you to specify the type of objects a class or method will work with at compile time. This improves code clarity, prevents runtime `ClassCastException` errors, and allows for more efficient code.

1. The Problem Before Generics

Consider an `ArrayList` before generics:
Generics basic error example import java.util.*; public class Main { public static void main(String[] args) { ArrayList<String> stringList = new ArrayList<>(); //Specify String as the type parameter stringList.add("Hello"); stringList.add(10); // no Compile-time error! String s = stringList.get(0); //No casting needed, type safety is ensured. System.out.print(s); } }
The compiler doesn't know what types of objects the `ArrayList` will contain. You can add anything, and retrieving objects requires explicit casting, which is error-prone. If you accidentally try to retrieve an integer as a string (e.g., `(String)list.get(1)`), a `ClassCastException` will occur at runtime.

2. Generics to the Rescue

Generics introduce type parameters, typically represented by capital letters within angle brackets (`<>`). These parameters act as placeholders for specific types. Let's see the improved version using generics:
Generics example - java import java.util.*; public class Main { public static void main(String[] args) { ArrayList<String> stringList = new ArrayList<>(); //Specify String as the type parameter stringList.add("Hello"); //stringList.add(10); //Compile-time error! This is now prevented. String s = stringList.get(0); //No casting needed, type safety is ensured. System.out.print(s); } }

Output

Hello
Now, the `ArrayList<String>` can *only* hold strings. The compiler enforces this restriction at compile time, preventing the addition of incompatible types and eliminating the need for casting. If you attempt to add an integer, the compiler will immediately report an error.

3. Defining Generic Classes

You can create your own generic classes. The type parameter is specified in the class declaration:
java generic class example // A generic class representing a box that can hold any type of item. class Box { private T item; // The item stored in the box. // Constructor to initialize the box with an item. public Box(T item) { this.item = item; } // Method to get the item from the box. public T getItem() { return item; } // Method to set the item in the box. public void setItem(T item) { this.item = item; } // A simple method to describe the box and its contents. public void describe() { System.out.println("This box contains a: " + item.getClass().getSimpleName()); System.out.println("The item is: " + item); } @Override public String toString() { return "Box{" + "item=" + item + '}'; } } public class Main { public static void main(String[] args) { // Creating a box to hold an Integer. Box integerBox = new Box<>(10); integerBox.describe(); int value = integerBox.getItem(); // No need for explicit casting! System.out.println("Value from integerBox: " + value); // Creating a box to hold a String. Box stringBox = new Box<>("Hello, Generics!"); stringBox.describe(); String message = stringBox.getItem(); // No need for explicit casting! System.out.println("Message from stringBox: " + message); // Creating a box to hold a custom object (e.g., a Dog). class Dog { private String name; public Dog(String name) { this.name = name; } @Override public String toString() { return "Dog{" + "name='" + name + '\'' + '}'; } } Box dogBox = new Box<>(new Dog("Buddy")); dogBox.describe(); Dog myDog = dogBox.getItem(); // No need for explicit casting! System.out.println("Dog from dogBox: " + myDog); // You can even create a box of a specific type of Number Box numberBox = new Box<>(3.14); numberBox.describe(); Number num = numberBox.getItem(); System.out.println("Number from numberBox: " + num); } }

Output

This box contains a: Integer The item is: 10 Value from integerBox: 10 This box contains a: String The item is: Hello, Generics! Message from stringBox: Hello, Generics! This box contains a: Dog The item is: Dog{name='Buddy'} Dog from dogBox: Dog{name='Buddy'} This box contains a: Double The item is: 3.14 Number from numberBox: 3.14
Here, `T` is a type parameter that acts as a placeholder. When you create instances of `MyGenericClass`, you specify the actual type (Integer, String, etc.) within the angle brackets. The compiler then uses this type information to ensure type safety within the class.

4. Generic Methods

Generics can also be applied to methods:
Java generic methods example class Dog { private String name; public Dog(String name) { this.name = name; } @Override public String toString() { return "Dog{" + "name='" + name + '\'' + '}'; } } public class Main { public static void printArray(E[] array) { for (E element : array) { System.out.println(element); } } public static void main(String[] args) { Dog[] dogs = {new Dog("Buddy"), new Dog("Max"), new Dog("Charlie")}; printArray(dogs); // Works with Dog array! } }

Output

Dog{name='Buddy'} Dog{name='Max'} Dog{name='Charlie'}
The `<E>` declares a type parameter for the method. The `printArray` method can now work with arrays of any type.

5. Multiple Type Parameters

A generic class or method can have multiple type parameters:
generic class with multiple type parameters public class Pair<K, V> { //K for key, V for value private K key; private V value; // ... constructor and getter methods ... }

6. Bounded Type Parameters

Sometimes you want to restrict the types that can be used as type parameters. This is done using bounded type parameters:
Java generic bounded type parameters example //change file name to NumberChecker.java to compile code public class NumberChecker { // T must extend Number public void checkNumber(T num) { System.out.println("Number value: " + num.doubleValue()); // Accessing doubleValue() which is common to all Numbers } public static void main(String[] args) { NumberChecker intChecker = new NumberChecker<>(); intChecker.checkNumber(10); // Works NumberChecker doubleChecker = new NumberChecker<>(); doubleChecker.checkNumber(3.14); // Works NumberChecker floatChecker = new NumberChecker<>(); floatChecker.checkNumber(2.71f); // Works NumberChecker longChecker = new NumberChecker<>(); longChecker.checkNumber(12345L); // Works // NumberChecker stringChecker = new NumberChecker<>(); // Compile-time error: String doesn't extend Number // Example of using a more specific Number type (e.g., BigDecimal) NumberChecker bigDecimalChecker = new NumberChecker<>(); bigDecimalChecker.checkNumber(new java.math.BigDecimal("1234567890.0987654321")); // Works // Example of using a Number subclass class MyNumber extends Number { //Custom Number class private double value; public MyNumber(double value) { this.value = value; } @Override public int intValue() { return (int)value; } @Override public long longValue() { return (long)value; } @Override public float floatValue() { return (float)value; } @Override public double doubleValue() { return value; } @Override public String toString() { return String.valueOf(value);} } NumberChecker myNumberChecker = new NumberChecker<>(); myNumberChecker.checkNumber(new MyNumber(99.9)); // Works } }

Output

Number value: 10.0 Number value: 3.14 Number value: 2.7100000381469727 Number value: 12345.0 Number value: 1.2345678900987654E9 Number value: 99.9
This ensures that `T` can only be a subclass of `Number`.

7. Wildcards

Wildcards (`?`) provide flexibility when dealing with generic types, particularly in situations involving collections. There are three main wildcard types: Unbounded Wildcard (`<?>`): Accepts any type. Useful when you don't care about the specific type. Upper Bounded Wildcard (`<? extends Number>`): Accepts `Number` and its subclasses. Useful when you need to read values from a collection, but you don't need to add values. Lower Bounded Wildcard (`<? super Integer>`): Accepts `Integer` and its superclasses. Useful when you need to add values to a collection, but you don't need to read specific types. In summary, generics are a crucial part of modern Java, providing a significant improvement in type safety, code clarity, and reusability. Understanding their various facets is essential for writing robust and maintainable Java code.

Tutorials